Over 310,000 Mwh of energy is curtailed each year on the Orkney Isles. This represents a significant portion of the total potential energy that can be generated on the isles. There are a large number of wind turbines there that are not being used to their full capacity currently due to insufficient demand and limited exporting capacity. This is results in lower revenue for Kaluza and wind generators, higher energy costs for residents, and increased wastage of renewable energy, going against government aims. We propose a Demand Response scheme that reduces the amount of curtailed energy by storing energy in smart heaters that can be used later in periods of high demand.
By modelling different scenarios, our analysis finds that the amount of curtailed energy can be reduced by between 1% and 5%, depending on the penetration rate and other assumptions. Assuming a Demand Response Penetration Rate of 25%, there will be a reduction of 8,700 MWh in curtailed energy, approximately equal to a 3% reduction. Given a high level of government subsidies for both energy costs and smart heaters, a breakeven point below 4 years is achievable. Furthermore, our DR scheme not only leads to a revenue increase for Kaluza, but also leads to lower energy bills for households ( ≈ -10%), additional revenue for wind generators and an overall increase in renewable energy consumption.
The Orkney Isles are the third windiest place in the United Kingdom. To exploit this fact, about 500 turbines were placed on the Isles to produce electricity for the residents and mainland Scotland through a cable that runs between the Orkney Isles and the mainland. However, when wind speed is high, some wind energy is curtailed, due to limited capacity of the export cable and insufficient demand from Orkney residents. These factors lead to a lost opportunity in revenue for both Kaluza and wind generators. This report will explore how installing smart heaters, which can be switched on when energy is being curtailed to store some of it for later use, would allow for usage of that curtailed energy. As this curtailed energy is additional revenue for Kaluza, we will explore how we can offer this energy to residents at a discounted rate while providing some returns for wind generators from the energy that would be otherwise unexploited. We hypothesize that using 5% to 10% of this curtailed energy across at least 20% of households in the Orkney Isles will lead to an increase in profit for Kaluza, but also benefit other stakeholders such as local households who would pay for electricity at a lower rate, and the government who would be able to reduce fuel poverty and increase renewable energy consumption through subsidising this project. By understanding how much energy is currently curtailed across the Orkney Isles each year, we will explore how to use that energy across different levels of demand response penetration rates and determine the optimal number of households required for this programme to be viable.
The Analysis for this project was done using standard python libraries, including pandas which allows for easy manipulations of large datasets and matplotlib.pyplot which was used for most visualizations. However, we used plotly for graphs that have a time component to allow each reader to get an overview of the graphs but also have the ability to go more in-depth into them by focusing on a smaller time interval.
As demand was the main focus of this analysis, we decided to put all of our data to the granularity of 30 minutes for the main analysis, which was the original granularity of the demand.
As our demand was for 2017, we decided to focus on that year. However, the power generation for 2017 had missing data for 3 weeks in May while the power generation for 2016 only had about a week of missing data at the end of September. Thus, we decided to use the turbine telemetry data from 2016 for 2017 to have as few missing values as possible in our models.
The following cells will go through importing the libraries we used for this report, importing the datasets, cleaning and normalizing them to account for possibles scale errors in the data. It will also include functions and the creation of new datasets that will be used later in the results section. All further analyses will be available in the Appendix.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import datetime
from datetime import timedelta
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import IPython
#from google.colab import drive
#drive.mount('/content/drive', force_remount=True)
#path = '/content/drive/MyDrive/{folder_with_HSO_data}/'.format(folder_with_HSO_data='HSO-data')
path = '/mnt/c/Users/Ivan/Desktop/Term2/aib/hso/'
We set the date column as the index for each dataset we imported as we only had one data point per date. This allowed for easier manipulation and visualization of the data. We also directly added the computation of the corresponding energy for each generated power output by assuming that each power output was a measure for the last minute interval. We considered using the timestamps to be as precise as possible but realized that we had missing dates in our dataframes for which we did not want to compute any energy and thus preferred setting the time interval of the generated power to 1 minute.
turbine_df = pd.read_csv(path + 'turbine_telemetry.csv', parse_dates=[0], index_col=0)
dmd=pd.read_csv(path +'residential_demand.csv', parse_dates=[0], index_col=0)
#We assume that the power measure is always for the past minute (to account for missing times in the dataset)
turbine_df['Energy_kwh']=turbine_df.Power_kw*1/60
We decided to normalize our demand data to the winter power peak demand provided by the Scottish & Southern Electricity Network Website [1] to ensure that the range of our demand matches the actual range of demand in the Orkney Isles.
We also set up the demand dataframe to include different categories such as the seasons, the hours of the day, and whether it's a weekday or a weekend. This will allow us to compare the demand across all those categories to understand its patterns better.
peak_dmd_kw = 35700 / 2 # divide by two because our time interval in 30 min
dmd['Demand_mean_mw'] = dmd.Demand_mean_kw / dmd.Demand_mean_kw.max() * peak_dmd_kw / 1000
dmd['Demand_mean_kw_ph'] = dmd.Demand_mean_kw / dmd.Demand_mean_kw.max() * peak_dmd_kw / 10000
dmd['month'] = dmd.index.month
dmd['weekday'] = dmd.index.weekday
dmd['day'] = dmd.index.day
dmd['hour'] = dmd.index.hour
dmd['minute'] = dmd.index.minute
dmd['season'] = dmd.index.month.map({12:'winter', 1:'winter', 2:'winter',
3:'spring', 4:'spring', 5:'spring',
6:'summer', 7:'summer', 8:'summer',
9:'autumn', 10:'autumn', 11:'autumn'})
dmd['mwh_mean_demand'] = dmd.Demand_mean_mw * 0.5
def demand_over_time(unit, df=dmd):
winter = df[df.season == "winter"]
spring = df[df.season == "spring"]
summer = df[df.season == "summer"]
autumn = df[df.season == "autumn"]
winter = winter.groupby(winter[unit]).mean().reset_index()
spring = spring.groupby(spring[unit]).mean().reset_index()
summer = summer.groupby(summer[unit]).mean().reset_index()
autumn = autumn.groupby(autumn[unit]).mean().reset_index()
plt.figure(figsize=(15,5))
if unit=='hour':
plt.xticks(np.arange(min(weekend.hour), max(weekend.hour)+1, 1))
plt.xlabel('Hour of the day')
plt.ylabel('Mean demand (MW)')
plt.title('Mean demand per hour of the day depending on the seasons')
sns.lineplot(x=winter[unit], y=winter.Demand_mean_mw,label="winter")
sns.lineplot(x=spring[unit], y=spring.Demand_mean_mw,label="spring")
sns.lineplot(x=summer[unit], y=summer.Demand_mean_mw,label="summer")
sns.lineplot(x=autumn[unit], y=autumn.Demand_mean_mw,label="autumn")
plt.legend(loc=2)
plt.show()
weekend = dmd[dmd.weekday.isin([5,6])]
weekday = dmd[dmd.weekday.isin(range(5))]
weekend = weekend.groupby(weekend.hour).mean().reset_index()
weekday = weekday.groupby(weekday.hour).mean().reset_index()
In the cell below, we created a dictionary that gave us the maximum power that one turbine can generate per wind speed. To do so, we computed the median of the generated power for all the given wind speeds under 30 m/s, which is the cut-out speed for our turbines. We will use this dictionary in the next part to get the maximum potential power for each of the wind speed data points we have in our turbine telemetry data.
# remove all missing values from the dataframe
t_df = turbine_df.dropna(0, inplace=False).copy()
# Median is taken to represent average output at given windspeed
max_power = t_df.groupby('Wind_ms').median()
max_power['Wind_ms']=max_power.index #add column for wind_speed (necessary ? could just use index)
# Adding a max power column that is the computed median power
max_power['Max_Power'] = max_power['Power_kw']
# Change max_power after 890 to 900
max_power.loc[max_power.Max_Power >= 890 , "Max_Power"] = 900
max_power.loc[max_power.Wind_ms >= 20 , "Max_Power"] = 900
# Change MaxPower to 0 for all wind speed above 30
max_power.loc[max_power.Wind_ms > 30, "Max_Power"] = 0
windspeed = max_power['Wind_ms'].tolist()
mp_dict = {}
for i in windspeed:
mp_dict[i] = max_power['Max_Power'][i]
The dictionary created previously was used to get the maximum potential energy for each of our wind speed data points. To do so, we used the same method as we previously did to compute the energy from our generated power. Afterwards, we normalized it, as we did for the demand, to account for the scale error and to extrapolate the data of 1 turbine to 500 turbines.
To do so, we had to determine the maximum energy output if our wind turbines were uncurtailed. Unfortunately, we did not have any data on this. The closest value we found was 57.1 MW as the total renewable capacity for generated power [1]. However, this number did not include the curtailed energy (as it is the generated power and not the maximum potential power) which is what we are particularly interested in. It is also extremely unlikely that the maximum power for all the 500 turbines wpuld be 900 kW at the same time. As we considered that the turbine we have the telemetry data for is representative of all the turbines, it is also representative of their power outputs over time. Thus, we took the median value of the maximum potential output we have computed as a way to represent to maximum output. This value was around 316 kWh for all the turbines. Normalizing our energy output allowed us to get a much more accurate picture of the energy creation of 500 turbines from the data of 1 turbine.
We used the telemetry data only from 2016, as it was the year that had a smaller interval of missing data and thus painted a closer picture of the actual behaviour of the turbine. We also excluded the time interval in September that had missing data so it was not included in the curtailment calculations. We then transposed the data from 2016 to 2017 through the shifting of 1 year of our dates. We also needed to exclude the 29 of February from 2016 as it was not a date in 2017.
We grouped the data per 30 minutes to match the frequency of the demand data. Then we added the energy demand which allowed us to compute the curtailed energy and the amount of energy exported through the cable. We also computed the shiftable energy, which is the maximum amount of energy that could be shifted when energy is curtailed to moments where energy is not curtailed. This ensured that we used as much of the curtailed energy as possible while also trying to export as much energy as possible to the mainland.
# dataframe that will have only 2016 data
turbine_df2 = turbine_df.copy()
# add max_power column to df
turbine_df2['Max_Power'] = turbine_df2['Wind_ms'].map(mp_dict)
# Drop NA values
turbine_df2 = turbine_df2.dropna(0, inplace=False)
#before cleaning the datasets, we will add to turbine_df the energy generated by the turbine (so we are not overestimating some time interval from cleaned rows)
turbine_df2['Max_Energy_kwh']=turbine_df2.Max_Power*1/60
Max_energy = 500*turbine_df2['Max_Energy_kwh'].median() # get the median energy for 1 turbine that will give you overall how the 500 turbines behave
# Normalizing our values and multiplying by max energy
turbine_df2.Max_Energy_kwh = turbine_df2.Max_Energy_kwh / 15 * Max_energy
#selecting data only for 2016
turbine_df2=turbine_df2[turbine_df2.index.year == 2016]
# Removing the period with Setpoint = 0 in Sept
new = turbine_df2[(turbine_df2.index.month == 9) & (turbine_df2.index.day > 17)& (turbine_df2.Setpoint_kw == 0)]
turbine_df2=turbine_df2[~turbine_df2.index.isin(new.index)]
turbine_30min=turbine_df2.groupby(pd.Grouper(freq="30min")).agg({'Max_Energy_kwh':'sum','Wind_ms':'mean'}).copy()
# 2017 has one day less in febury (29/02) than 2016 so let's take that day off
feb_2016_index=turbine_30min[(turbine_30min.index>'2016-02-29')&(turbine_30min.index<'2016-03-01')].index
turbine_30min=turbine_30min[~turbine_30min.index.isin(feb_2016_index)]
turbine_30min.index = pd.to_datetime(turbine_30min.index) + pd.to_timedelta(365, unit = 'd')
new_2 = new.groupby(pd.Grouper(freq="30min")).sum().index
new_2 = pd.to_datetime(new_2) + pd.to_timedelta(366, unit = 'd')
turbine_30min=turbine_30min[~turbine_30min.index.isin(new_2)]
turbine_30min['Total_demand_mwh']=dmd.groupby(pd.Grouper(freq="30min")).sum()['mwh_mean_demand']
turbine_30min["Total_max_energy_mwh"] = turbine_30min["Max_Energy_kwh"] /1000
turbine_30min["Curtailed_energy_mwh"]=turbine_30min["Total_max_energy_mwh"]-(turbine_30min["Total_demand_mwh"]+20)
turbine_30min['Curtailed_energy_mwh'].loc[turbine_30min['Curtailed_energy_mwh'] < 0] = 0
turbine_30min=turbine_30min[turbine_30min.index.year ==2017]
turbine_30min[turbine_30min["Curtailed_energy_mwh"] >500]=0
# add exported energy line
turbine_30min['Exported_mwh'] = turbine_30min["Total_max_energy_mwh"]-turbine_30min["Total_demand_mwh"]
turbine_30min['Exported_mwh'].loc[turbine_30min['Exported_mwh'] > 20] = 20
# We are creating a column that will tell us the possible energy that can be shifted from the demand according to the conditions below
turbine_30min.loc[(turbine_30min.Total_max_energy_mwh>20)&(turbine_30min.Total_max_energy_mwh-turbine_30min.Total_demand_mwh<20),"Shiftable_energy"]=20-(turbine_30min.Total_max_energy_mwh-turbine_30min.Total_demand_mwh)
turbine_30min.loc[turbine_30min.Total_max_energy_mwh<=20,"Shiftable_energy"]=turbine_30min.Total_demand_mwh
We computed the number of households that would need to have a smart heater for the amount of suitable energy we have over time. This allowed us to get the number of households for different levels of demand response penetration. We supposed that the household would only have one smart heater and took into account its input and output capacity as well as its overall capacity and the fact that it can store energy for up to 24 hours. We tried to maximize the usage of the heaters to the demand of the households which would allow us to shift the most demand with as few heaters as possible. We also tried to get the curtailed energy as close in time as possible to the time where the households would use it as the longer energy is stored, the more likely you are to have losses. 7 columns in turbine_30min were created for this computation (input and outputs) such that:
%%time
pd.options.mode.chained_assignment = None
turbine_30min['Total_demand_kwh_household'] = turbine_30min['Total_demand_mwh']/5000*1000
turbine_30min['New_overall_demand_kwh'] =0 #it will be 0 if unchanged
turbine_30min['Amended_curtailement_kwh']=turbine_30min.Curtailed_energy_mwh*1000
turbine_30min['Max_households'] = 0 #initialising the column
turbine_30min['Current_input_kwh'] = 0 #initialising the column
turbine_30min['Current_storage_kwh'] = 0 #current electricity in storage in all torage heaters (assumed the same for every household)
turbine_30min['Amount_demand_shifted_kwh'] = 0
months_2017=turbine_30min.index[48:] #starting on the 2nd day to get the last 24h for our storage
exclude=turbine_30min[(turbine_30min.index>='2017-02-28')&(turbine_30min.index<'2017-03-02')|(turbine_30min.index>='2017-09-30')&(turbine_30min.index<'2017-10-02')].index
months_2017=months_2017[~months_2017.isin(exclude)]
for i in months_2017:
if turbine_30min.Curtailed_energy_mwh.loc[i]==0 and turbine_30min.Shiftable_energy.loc[i]!=0: #then we can shift energy, else we jut need to move on to the next date
if turbine_30min.Total_demand_kwh_household.loc[i]<=0.75:
#then we can output all the stored energy needed at that time
to_shift=turbine_30min.Total_demand_kwh_household.loc[i]
else: #we limit to 0.75kWh the amount of energy
to_shift=0.75
#now we will compute how many households it represent (taking the lower closer int)
households=int(turbine_30min.Shiftable_energy.loc[i]*1000/to_shift) #from MWh to kWh
#now we need to see if households * to_shift can be taken from excess curtailed energy in the past 24h
day_before=pd.date_range(start=i-pd.Timedelta('1 day'), end=i, freq='30min')[:48]
j=47
alloted_energy=0
temp_to_shift=to_shift
temp_households=households
while j>=0 and alloted_energy<households*to_shift-0.5: #while we haven't alloted all energy to curtailement moments, we need to keep looking (energy about interval value not == bc int approximations)
if turbine_30min.Amended_curtailement_kwh.loc[day_before[j]]>temp_to_shift and turbine_30min.Current_input_kwh.loc[day_before[j]]<1.65: #if we have some curtailement to use for at least 1 household
if turbine_30min.Amended_curtailement_kwh.loc[day_before[j]] < temp_households * temp_to_shift:
#we need to adapt the number of households for that energy available
temp_households=int(turbine_30min.Amended_curtailement_kwh.loc[day_before[j]]/to_shift)
if turbine_30min.Current_input_kwh.loc[day_before[j]]+temp_to_shift>1.65: #then we need to adjust how much we shift to match the input rate
#we need to adapt how much we are shifting for all the household because not enough input left
temp_to_shift=1.65-turbine_30min.Current_input_kwh.loc[day_before[j]]
#we need to adapt how much we are shifting for all the household if not enough storage left from then to now
storage=0
for l in pd.date_range(start=day_before[j], end=i, freq='30min'):
if turbine_30min.Current_storage_kwh.loc[l]>=23:
j-=1
break #not enough storage in between now and that time for the energy we want so need to break
elif turbine_30min.Current_storage_kwh.loc[l]+temp_to_shift>23:
if storage>0:
storage=min(23-turbine_30min.Current_storage_kwh.loc[day_before[j]],storage)
else:
storage=23-turbine_30min.Current_storage_kwh.loc[day_before[j]]
elif turbine_30min.Current_storage_kwh.loc[l]+temp_to_shift<=23:
if storage>0:
storage=min(storage,temp_to_shift)
else:
storage=temp_to_shift
if storage>0:
temp_to_shift=storage
alloted_energy+=temp_households * temp_to_shift
turbine_30min.Current_input_kwh.loc[day_before[j]]+=temp_to_shift
turbine_30min.Amended_curtailement_kwh.loc[day_before[j]]-=temp_households * temp_to_shift
turbine_30min.New_overall_demand_kwh.loc[i]=turbine_30min.Total_demand_kwh_household.loc[i]-temp_to_shift #it will be 0 if unchanged[day_before[j]]=turbine_30min.Amended_curtailement[day_before[j]]-allotted_energy
turbine_30min.New_overall_demand_kwh.loc[day_before[j]]=turbine_30min.Total_demand_kwh_household.loc[day_before[j]]+temp_to_shift
turbine_30min.Max_households.loc[day_before[j]]=max(temp_households,turbine_30min.Max_households.loc[day_before[j]])
turbine_30min.Amount_demand_shifted_kwh[day_before[j]]+=temp_households * temp_to_shift
#need to add it to storage up to the time it is set to be consumed (index i)
for l in pd.date_range(start=day_before[j], end=i, freq='30min'):
turbine_30min.Current_storage_kwh.loc[l]+=temp_to_shift #current load of the smart heater
#we still have households*to_shift - alloted_energy to allocate
temp_households=int((households*to_shift - alloted_energy)/to_shift)
temp_to_shift=to_shift
j-=1
The cell below computes the amount of shifted demand for a certain percentage (rate) of households selected. It also creates lists that will be used in the analysis to plot the amount of energy shifted per number of households in the program.
turbine_each_rate=turbine_30min.copy()
def shifted_demand_rate(rate):
turbine_each_rate['above_'+str(rate)+'%']=turbine_each_rate[turbine_each_rate.Max_households>100*rate].Current_input_kwh*100*rate
return (turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['above_'+str(rate)+'%']+turbine_each_rate[turbine_each_rate.Max_households<=100*rate].groupby(pd.Grouper(freq="M")).sum()['Amount_demand_shifted_kwh'])/1000
shifted_demand=[]
for i in range(5,55,5):
shifted_demand.append(shifted_demand_rate(i).sum())
The cell below computes for different rates of households onboarded onto the DR scheme the amount of energy they could shift as a part of their whole demand.
#Input of shiftable energy per DR peneration rate
shiftable_energy=turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['above_10%']/(turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['Total_demand_mwh']/5*1000)+turbine_each_rate[turbine_each_rate.Max_households<=1000].groupby(pd.Grouper(freq="M")).sum()['Amount_demand_shifted_kwh']/(turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['Total_demand_mwh']*1000/5)
s_energy = pd.DataFrame(shiftable_energy, columns=[0.1])
s_energy[0.25]=turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['above_25%']/((turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['Total_demand_mwh']/2)*1000)+ turbine_each_rate[turbine_each_rate.Max_households<=2500].groupby(pd.Grouper(freq="M")).sum()['Amount_demand_shifted_kwh']/(turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['Total_demand_mwh']*1000/2)
s_energy[0.5]=turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['above_50%']/(turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['Total_demand_mwh']*1000)+turbine_each_rate[turbine_each_rate.Max_households<=5000].groupby(pd.Grouper(freq="M")).sum()['Amount_demand_shifted_kwh']/(turbine_each_rate.groupby(pd.Grouper(freq="M")).sum()['Total_demand_mwh']*1000)
The feasibility model is calculated through the estimate_monthly_figures function, which takes inputs from curtailed energy and demand datasets. Additionally, the model relies on the mnth function which helps to convert hourly values into monthly ones.
estimate_monthly_figures takes in a set of parameters defined by an auxiliary input generation function and returns a dictionary of values from which the dataset is constructed. We will evaluate our strategies based on the resulting dataset. The model computes government subsidies, costs and revenue, and makes the distinction between the first year when the smart heaters are installed, which implies additional costs, and all subsequent years.
def mnth(x):
return x*24*30.4
# PR - Penetration Rating
# HG - Heater Grant
# S - Subsidy by gvmnt
# TD - Total Discount (always 1 in first year)
# install - Binary var for installing heaters
def estimate_monthly_figures(year, month, curt_prcnt, hourly_demand, install, PR, HG, S, TD):
elec_price = 0.156
elec_cost = 0.12
elec_cost_dc = 0.10
n_households = int(10731 * PR)
revenue_pm = mnth(elec_price * hourly_demand) # price they were paying before scheme
revenue_pm_scheme = revenue_pm + \
curt_prcnt * mnth(elec_price * (1 - TD) * hourly_demand)
customer_savings = mnth((elec_price - revenue_pm_scheme / mnth(hourly_demand * (1 + curt_prcnt))) * hourly_demand)
costs_pm = mnth(elec_cost * hourly_demand)
if install:
fixed_pm = 1 + 100/12 + 600 * (1 - HG) / 12
gov_subs = mnth(curt_prcnt * hourly_demand * elec_cost * S) + (600 * HG / 12)
costs_pm_scheme = fixed_pm + mnth(elec_cost + curt_prcnt * elec_cost_dc * (1 - S)) * hourly_demand
usage_increase = 1
else:
fixed_pm = 1
gov_subs = mnth(curt_prcnt * hourly_demand * elec_price * S)
costs_pm_scheme = fixed_pm + mnth((elec_cost + curt_prcnt * elec_cost_dc) * hourly_demand) - gov_subs
usage_increase = 1.1
# Gross profit per household
gp_ph = revenue_pm - costs_pm
gp_ph_scheme = (revenue_pm_scheme - costs_pm_scheme) * usage_increase
scheme_gp_delta = gp_ph_scheme - gp_ph
return {
'dt': datetime.date(year, month, 1),
'year': year,
'month': month,
'revenue_pm': revenue_pm,
'revenue_pm_scheme': revenue_pm_scheme,
'customer_savings': customer_savings,
'monthly_costs': costs_pm,
'monthly_costs_scheme': costs_pm_scheme,
'monthly_profit': gp_ph,
'monthly_profit_scheme': gp_ph_scheme,
'profit_diff': scheme_gp_delta,
'gov_subsidy': gov_subs,
'n_households': n_households,
'PR': PR
}
These functions were used in order to generate inputs for the feasibility model:
tabulate simply creates a pandas dataframe based on model inputs generated by generate_year and generate_scenario functions. The dataframe is used for strategy evaluation and visualization
generate_year uses demand and energy curtailment monthly data to construct a yearly estimate of these monthly figures
generate_scenario creates a 7-year scenario comprised of yearly figures produced by generate_year function
overview simply groups a tabulated scenario by year in order to quickly assess the performance of the project under given assumptions
avg_hourly_demand_pm = dmd.groupby('month').mean().Demand_mean_kw_ph
def tabulate(scenarios):
data = [estimate_monthly_figures(*s) for s in scenarios]
scenario_figures = pd.DataFrame(data).round(2)
return scenario_figures
def generate_year(year, install, PR, HG, S, TD):
months = []
for month in range(1, 13):
demand = avg_hourly_demand_pm.iloc[month - 1]
shiftable_energy = s_energy[PR].iloc[month - 1]
months.append((year, month, shiftable_energy, demand, install, PR, HG, S, TD))
return months
def generate_scenario(PR, HG, S, S2, TD, n_years=7):
y1 = generate_year(2018, 1, PR, HG, S, 1)
for n in range(n_years):
y1.extend(generate_year(2019+n, 0, PR, HG, S2, TD))
return tabulate(y1)
def overview(scenario):
group = scenario.groupby('year').sum()
group.n_households = group.n_households / 12
break_even = abs(group.profit_diff.iloc[0]) / group.profit_diff.iloc[1] + 1
return group[group.columns.drop(['month', 'PR'])]
millions is a simple formatter that replaces trailing zeros in large numbers by M denoting millions
plot_scenario a function which produces a set of lineplots across specified axes and includes a title, it is used to visualize outputs of estimate_monthly_figures model
from matplotlib.ticker import FuncFormatter
def millions(x, pos):
return '%1.1fM' % (x * 1e-6)
formatter = FuncFormatter(millions)
def plot_scenario(scenario, ax_c, title='Scenario'):
df = scenario.copy()
df['total_costs'] = df.monthly_costs_scheme * df.n_households
df['total_profit_diff'] = df.profit_diff * df.n_households
df['total_abs_profit'] = df.monthly_profit_scheme * df.n_households
df['total_revenue'] = df.revenue_pm_scheme * df.n_households
df['running_profit_diff'] = df.total_profit_diff.cumsum()
sns.set_style("darkgrid")
sns.lineplot(data=df, x='dt', y='total_revenue', label='Revenue', ax=ax_c)
sns.lineplot(data=df, x='dt', y='running_profit_diff', label='Cumul. profit diff ', ax=ax_c)
sns.lineplot(data=df, x='dt', y='total_costs', label='Costs', ax=ax_c)
ax_c.axvline(x=df[df.running_profit_diff > 0].dt.iloc[0], ls='--', color='grey')
ax_c.axhline(y=0, ls='-', color='grey')
ax_c.yaxis.set_major_formatter(formatter)
ax_c.set_xlabel('Year')
ax_c.set_ylabel('Money (£)')
ax_c.legend(loc='lower center')
ax_c.set_title(title)
# Scenarios 1 - 3, 10% market penetration
sc1 = generate_scenario(PR=0.1, HG=0.5, S=0.4, S2=0.2, TD=0.25)
sc2 = generate_scenario(PR=0.1, HG=0.75, S=0.5, S2=0.15, TD=0.25)
sc3 = generate_scenario(PR=0.1, HG=1.0, S=0.7, S2=0.1, TD=0.25)
# Scenarios 4 - 6, 25% market penetration
sc4 = generate_scenario(PR=0.25, HG=0.5, S=0.4, S2=0.4, TD=0.5)
sc5 = generate_scenario(PR=0.25, HG=0.75, S=0.5, S2=0.35, TD=0.5)
sc6 = generate_scenario(PR=0.25, HG=1.0, S=0.7, S2=0.3, TD=0.5)
# Scenarios 7 - 9, 50% market penetration
sc7 = generate_scenario(PR=0.5, HG=0.5, S=0.7, S2=0.7, TD=0.75)
sc8 = generate_scenario(PR=0.5, HG=0.75, S=0.7, S2=0.65, TD=0.75)
sc9 = generate_scenario(PR=0.5, HG=1.0, S=0.7, S2=0.6, TD=0.75)
The telemetry data used in this analysis came from one turbine in the Orkney Isles. We can see in the first graph below that a large proportion of that power is very small, which means that the turbine would be off or at very low power generation quite often during a year. We can see that low values of power are more frequent than higher values of power, which could reflect low wind speeds or low power outputs coming from imposed curtailment. Finally, we see that the turbine seems to reach a maximum output of around 900kW quite often during the year which would imply that power generation would be very high at times and much lower at others. The Technical Description [2] of the turbine confirms that the maximum output for this turbine is 900 kW. By looking at the power generation over time, we can confirm that the power generated seems to fluctuate from 0 to 900 kW over the year without a specific trend and would depend on the wind speed at different times. The second graph below shows the distribution of the wind speed. We can note that wind speeds above 25 don’t seem to be very frequent compared to other wind speeds which are probably better for wind turbines considering they cannot operate at very high wind speed. While low wind between 0 and 5 m/s are less frequent than wind speeds between 5 and 15 m/s which means that we will encounter high speeds more frequently than lower speeds and thus have curtailed energy more often than uncurtailed energy if we suppose that energy is often curtailed at high wind speed while it is uncurtailed at low speeds (since less power would be generated at low speed).
turbine_df.Power_kw.hist(figsize=(15,5))
plt.xlabel('Power generated by one turbine (kW)')
plt.ylabel('Count')
plt.title('Distribution of the power generated')
plt.figure()
turbine_df.Wind_ms.hist(figsize=(15,5))
plt.xlabel('Wind speed from one turbine (m/s)')
plt.ylabel('Count')
plt.title('Distribution of the wind speed')
plt.show()
The demand data used in this analysis comes from the mean demand in power from a sample of households across the Orkney Isles. We know from the Orkney’s Fuel Poverty Strategy 2017-2022 [3] that about 50% of the households in the Orkney Isles use electricity for heating while about 40% use oil. As prices of oil have gone up in the last years, more and more residents turn to electricity for heating. Thus, we could say that Kaluza’s customer base is about 50% of the households in Orkney. As Orkney has about 10,000 households [4], we can assume that Kaluza’s customer base is about 5,000 households that use electricity for heating. While the Orkney Isles do not offer high temperatures, heating is still not necessary at the same levels over the year. The graph below shows the average demand for power from households for each hour of the day across seasons. As expected, we can see that the demand is higher over each hour of the day in the winter and lower in the summer. Spring and autumn seem to have relatively similar demands over the day. We can also notice that independently of seasons, demand is very low before 6 am, is low but stable from 7 am to 3 pm, peaks from 4 pm to 8 pm, and decreases from then. Hence, it seems like households use energy the most at the end of the day, probably when they are at home after coming back from school or work. If people have a higher demand for electricity when they are at home, we should observe a difference in demand between weekdays and weekends.
demand_over_time('hour')
The graph below confirms this assumption, as we can observe that demand is overall higher during the weekend and more specifically, it is higher from 8 am to 4 pm when residents are at home during the weekend and probably not during the week. We can also observe that the demand is higher for the weekday from 5 am to 7 am, which would be when residents are preparing for work or school during the weekdays, but most likely asleep during the weekends. Overall, demand is high during weekends and during peak-hours which are from 4 pm to 8 pm. We can suppose that we will have to shift demand from those times (as demand is high, energy has less chance to be curtailed during those moments as it would be used by demand) to times where demand is lower.
plt.figure(figsize=(15,5))
plt.plot(weekend.hour, weekend.Demand_mean_mw,label="weekend")
plt.plot(weekday.hour, weekday.Demand_mean_mw,label="weekday")
plt.legend()
plt.xticks(np.arange(min(weekend.hour), max(weekend.hour)+1, 1))
plt.xlabel('Hour of the day')
plt.ylabel('Mean demand (MW)')
plt.title('Mean demand per hour of the day depending if weekday or weekend')
plt.show()
In order to evaluate the total amount of curtailed energy in the Orkney Isles, we must first determine what curtailed energy actually is. We define curtailed energy as:
Curtailed energy = Total potential energy - Energy consumed - Energy exported (Capacity of export cable at 40MWh).
To get the amount of energy curtailed annually across the Orkney Isles, we need to compute the total potential energy that could have been possibly generated at the wind speed by all the 500 turbines on the islands.
First, we determined the maximum power that can be generated by a wind turbine at a given wind speed. To do so, we plotted below in green the repartition of the power generated by one wind turbine depending on the wind speed it is subjected to:
plt.figure(figsize = (15,5))
turbine_df.plot.scatter('Wind_ms', 'Power_kw', alpha=0.01, color='green',figsize = (20,5), label='Generated Power')
max_power.Max_Power.plot(color='blue', lw=4, label='Maximum power for that wind speed')
plt.legend()
plt.xlabel('Wind speed (m/s)')
plt.ylabel('Power (kW)')
plt.title('Generated power for each wind speed')
plt.show()
We can see that a pattern emerges. The power output starts to increase after a wind speed of 5 m/s is reached and then plateaus around 14 m/s. Furthermore, we can see that power values after a wind of 25 m/s starts to become rarer.
We can also notice that most of the data points are concentrated to form a specific line. This line displays the maximum power that one turbine can generate at each wind speed. More specifically, this maximum power at each wind speed comes from the median power output in all our generated powers.
We considered the cut-out speed of our turbine as 30 m/s as the case where the wind speed was above 25 m/s for an average of 10 minutes represented less than 1% of our data (the computation can be seen in the Appendix).
Furthermore, we set the upper limit of the curve to 900 kW. In the dataset provided, the maximum power often goes slightly above 900 kW, but since the maximum setpoint is 900 kW, we are assuming this to be the max power output possible. This is confirmed by the Technical Description of the turbines that confirm that the maximum power for the turbine is 900 kW [2].
Finally, we get the maximum power output function plotted in blue in the graph above that will provide the maximum potential power in kW at a given wind speed.
Now that we have analyzed both our datasets and computed our maximum power model, we can compute the curtailed energy. Details on how the dataset that was used to compute this has been created, cleaned, and normalized can be found in the technologies and techniques section.
We can define the curtailed energy as the maximum potential energy that could have been generated at the wind speed by all the turbines and subtracting from that the energy that is actually used, which is the energy consumed by the households through their demand and the energy that is sent to mainland Scotland through the 40MW cable:
Curtailed energy = Total potential energy - Energy consumed - Energy exported (Capacity of export cable at 40MW).
Currently, 310395 MWh of energy is curtailed annually across the Orkney Isles, which is 52% of the maximal potential energy produced in a year, and thus 48% of the maximum potential energy is actually used per year. This means that a very large proportion of energy is currently curtailed in the Orkney Isles and we would like to use some of it through the scheme that this report is introducing.
Moreover, in the graph below, we can see that most of the time curtailed energy forms a good proportion of the maximum potential energy, while the demand and the exported (or imported when negative) energy represents a much smaller proportion of the maximum potential energy. When we zoom in on a few days in the graph using plotly interactive feature, we can observe that the curtailed energy is very often much higher than the household consumption and the exported energy. During those times, we can observe that the latter is at its maximum of 20 MWh for a period of 30 minutes. We can also see that when there is no curtailment, the maximum potential energy is generally quite low while the demand seems to stay at similar levels, and energy is very often imported. This means that while curtailment does have a relation to demand levels, it mainly occurs when wind speeds are low (and thus the maximum energy is low too).
As all curtailed energy is wasted, which not only is not optimal for energy efficiency but is also a loss of revenue for the wind generators and Kaluza, we need to find a method to use some of that curtailed energy.
print('Total amount of curtailed energy yearly in MWh: ')
print(turbine_30min["Curtailed_energy_mwh"].sum())
print('Total amount of maximum potential energy yearly in MWh:')
print(turbine_30min["Total_max_energy_mwh"].sum())
# Create traces
fig = make_subplots()
fig.add_trace(go.Scatter(x=turbine_30min.index, y=turbine_30min.Total_demand_mwh, name='Demand energy'))
fig.add_trace(go.Scatter(x=turbine_30min.index, y=turbine_30min.Total_max_energy_mwh, name='Maximum energy'))
fig.add_trace(go.Scatter(x=turbine_30min.index, y=turbine_30min.Curtailed_energy_mwh, name='Curtailed energy'))
fig.add_trace(go.Scatter(x=turbine_30min.index, y=turbine_30min.Exported_mwh, name='Exported/Imported Cable energy'))
fig.update_xaxes(title_text="Time")
fig.update_yaxes(title_text="Energy (MWh)")
fig.update_layout(title_text="Maximum, curtailed, exported/imported and demand energy across 2017")
fig.show()
First, we computed the maximum possible shiftable energy, which is the maximum amount of demand that can be shifted from a time period when energy is uncurtailed to when energy is curtailed such that:
We get that the maximum possible amount of shiftable energy is 26,110 MWh while the overall demand for 2017 is 65,296MWh. Thus, we could potentially shift 40% of our demand to possibly use curtailed energy.
We can see in the graph below that shiftable energy periods occur frequently. By zooming in on a few days using the interactive features in plotly, we can shift energy from uncurtailed periods for a majority of the time, as there are regularly periods of curtailed energy prior to periods of uncurtailed energy. We can also note that usually, our shiftable energy is our demand (first case above) and since this demand is for 5000 households, it is very likely that we would need a penetration rate of 50% (so all 5000 households) to shift all of this shiftable energy.
print('The amount of shiftable energy is:')
print(turbine_30min.Shiftable_energy.sum())
print('The demand in energy is:')
print(turbine_30min.Total_demand_mwh.sum())
# Create traces
fig = make_subplots()
fig.add_trace(go.Scatter(x=turbine_30min.index, y=turbine_30min.Total_demand_mwh, name='Demand energy'))
fig.add_trace(go.Scatter(x=turbine_30min.index, y=turbine_30min.Curtailed_energy_mwh, name='Curtailed energy'))
fig.add_trace(go.Scatter(x=turbine_30min.index, y=turbine_30min.Shiftable_energy, name='Shiftable energy'))
fig.update_xaxes(title_text="Time")
fig.update_yaxes(title_text="Energy (MWh)")
fig.update_layout(title_text="Curtailed, demand and shiftable energy across 2017")
fig.show()
To move that shifted energy and use curtailed energy, we propose using smart heaters, that could be turned on when energy is curtailed, and that could release the energy stored when there is demand for electricity. To do so, we have chosen to consider that each household that would join this scheme would be given a smart heater from Kaluza. This smart heater would be the Quantum Heater QM150 [5] that has a storage capacity of 23 kWh for 24h, an input rating of 3.3 kW, and an output rating of 1.5kW. Using those constraints, we computed using the demand for 5,000 households (Kaluza’s current customer base), the maximum number of households over time that could be onboarded on the scheme to use as much energy as possible (the computation is done in the Technologies and Techniques section). This maximum number of households represents the maximum number of households at each time interval that could use curtailed energy using the smart heaters. If 100% of Kaluza’s customers would join the scheme, which is 50% of the households in the Orkney Isles, and thus a penetration rate of 50%, we found that the maximum amount of demand that can be shifted is 13,972 MWh which account for 22% of the overall demand or 4.5 of the curtailed energy.
print('The amount of demand that can be shifted for a penetration rate of 50% is:')
print(shifted_demand_rate(50).sum())
We then plotted below the distribution of the maximum number of households that should participate to Kaluza's scheme to shift energy. We can see that, as expected, the optimal penetration rate would be 50%, which is all the customer base of Kaluza.
We chose to focus on Kaluza's customer base rather than try and attract additional customers as we learned previously that 40% of households in the Orkney Isles use oil for heating which is much cheaper than electricity and thus we could most likely not attract them to our scheme, even with discounted prices. In fact, curtailed energy would represent about 20% of their energy and they would still pay the normal price for the rest of their demand in electricity, which would lead to still higher heating price for electricity than oil.
turbine_30min[(turbine_30min.Max_households>0)].Max_households.hist(figsize = (15,5))
plt.xlabel('Maximum number of households')
plt.ylabel('Count')
plt.xlim(0,5000)
plt.title("Distribution of the maximum number of households that need to participate in Kaluza's scheme to shift as much energy a possible")
plt.show()
The first graph below shows the possible amount of energy that could be shifted for penetration rates going from 5% to 50%. The second graph shows how this possible shifted amount impacts the total curtailed energy and by how much percent we can decrease the amount of curtailed energy annually for different penetration rates.
plt.figure(figsize=(15,5))
plt.plot([i for i in range(5,55,5)], shifted_demand)
plt.xticks([i for i in range(5,55,5)])
plt.ylabel('Possible shifted demand energy (MWh)')
plt.xlabel('Penetration rate in %')
plt.title("Possible shifted demand energy for different DR penetration rates")
plt.show()
curtailed_energy=310395
proportion_of_curtailed=[(curtailed_energy-i)/curtailed_energy *100 for i in shifted_demand]
plt.figure(figsize=(15,5))
plt.plot([i for i in range(5,55,5)], proportion_of_curtailed)
plt.ylabel('Percentage decrease in curtailed energy')
plt.xlabel('Penetration rate in %')
plt.title("Decrease in curtailed energy for different level of DR peneration rates")
plt.show()
We see that the optimal penetration rate would be 45%. However, this is most likely unrealistic as it would involve paying for a smart heater for 4,500 households which would mean that it would take decades for Kaluza to break-even, or that an unrealistic amount of government grants would have to be obtained for this scheme.
In order to understand the value of the DR scheme, we need a model to assess the impact of different assumptions on Kaluza's costs, revenues and profits. A technical breakdown of our model can be found in the Technologies and Techniques part of this report. Here, however, we will provide a brief explanation of the model parameters, our assumptions and the logic behind them, as well as how our model works.
Kaluza has a one-time installation cost of £100 for each smart heater. They will cover this cost, along with a £1 monthly maintenance cost for the smart heaters. Based on market research these heaters can be purchased for £600 [5]. There are several options regarding which party pays for the heater, which we will account for in the model. However, the cost will be paid by some combination of Kaluza, the government, and the household. For Kaluza, the government covering the entire cost of the smart heater is equivalent to the government covering a certain portion with the household paying the rest of the cost as in both situations, Kaluza does not contribute anything towards the heater. However, the larger the government grant given for the cost of heaters, the lower the subsidy they are likely to give to customers for the curtailed electricity. We amortize the installation costs and smart heater costs over one year. While our scenarios assume that the government and Kaluza will cover the costs of smart heater purchase, we are positive that the vast majority of customers would be able to contribute to the smart heater purchase costs at a substantial discount rate, therefore exerting less pressure on the Scottish government.
Each household participating in the scheme will have the same model of heater installed. This is the Dimplex Quantum Heater QM150 [5]. This heater has an input rating of 3300W, an output rating of 1500W, and a maximum storage capacity of 23100 Wh. This means that a single smart heater can store over 23 kWh of energy. We assume that this energy can be stored for a period of 24 hours before it is lost. This is key to the scheme. The input rating of 3300W means that the smart heater can "charge" at a rate of 3.3 kWh. Therefore, it would take 7 hours of "charging" for the smart heater to reach its full capacity. The output rating of the smart heater refers to the rate at which the smart heater can release its stored energy. The QM150 has an output rating of 1500W, meaning it can release 1.5 kWh in an hour. It would take almost 15.5 hours for the smart heater to release all of its energy if the heater was at full capacity and at full output. We assume that the heater can "charge" and output energy at the same time.
We assume that Kaluza charges its customers the average north of Scotland electricity price of £0.1561 per kWh of energy [3]. Kaluza also sells the energy that it exports to the mainland at the same retail price. Kaluza purchases this energy from wind generators before selling it at a markup. We assume that this markup is equal to £0.0361, or roughly 30%, giving a purchase price of energy of £0.12 per kWh. We assume a relatively similar markup for the wind generators, estimating their cost of production for 1 kWh of energy as being equal to £0.09.
Kaluza is able to purchase the shifted energy that it stores in smart heaters at a discount to its usual price. The smart heaters will be notified when energy from the wind turbines is about to be curtailed. When this happens, the smart heaters will be turned on and begin storing energy to be used later, therefore generating additional demand and reducing curtailment. As the wind generators will now be making money on energy that would have been wasted without the scheme, we posit that they will be willing to accept this discount, as long as the price Kaluza pays for this energy is greater than their cost of production. We will use a discounted purchase price of £0.10 per kWh in our models, satisfying both Kaluza and the wind generators.
Customers do not have to pay for the curtailed energy that they use in the first year of the DR scheme. They effectively get this energy at a 100% discount. This discount is paid for by Kaluza and the government. We assume that the government will be willing to cover a certain percentage of Kaluza's costs during this time period. Kaluza will have to make up the remainder, meaning they will be making a loss on this curtailed energy that is being used in the first year of the scheme. This, coupled with the potentially large fixed costs coming from the installation and equipment costs, typically lead to large losses in the first year with the scheme becoming profitable thereafter.
As energy is cheaper and customers are getting smart heaters at a very low cost under the DR scheme, it is reasonable to assume that they will use more of this cheaper energy. Therefore, we assume that energy usage will increase by 10% throughout the scheme. This can be further supported by the levels of fuel poverty in the Orkney Isles. [3]
There are 5 main parameters that we change when modeling the DR scheme. They are as follows:
Penetration Rate (%)
Heater Grant (%)
Subsidy Year 1 (%)
Subsidy Post Year 1 (%)
Total Discount (%)
We first model revenue, costs, and profits that Kaluza receives per each household whom we sign up to the scheme, before multiplying this out across every signed-up household to get the total revenue and profit generated for Kaluza. The number of participating households is a function of the Penetration Rate that we set and the total number of households in Orkney. We take the total number of households in Orkney from the residential demand dataset and find it equal to 10,731 households.
Note: We have previously assumed that only ~5,000 households on the Isles are using electricity for heating, therefore our Penetration Rate will never exceed 50% of the total number of households in any of our scenarios.
Example Model Output
Given the input parameters:
sc_example = generate_scenario(PR=0.1, HG=0.75, S=0.5, S2=0.15, TD=0.25, n_years=1)
overview(sc_example)
The model returns monthly figures for Kaluza both providing curtailed energy via DR scheme and figures for selling wind energy without investing money in curtailment. The break-even analysis uses the difference between these figures as an opportunity cost during the first year.
All of our calculations are performed on a monthly basis to capture some of the seasonality in both demand and the amount of energy we can shift. We calculate the revenue that Kaluza makes per customer each month before implementing the scheme, which is equal to the average demand for a given month multiplied by the retail price, both in terms of kWh. We then find the revenue that Kaluza makes per household each month after implementing the scheme. This is equal to the revenue generated before the scheme plus revenue generated from shifted energy sold at a discount to households. We also take into account the increase in demand as a result of the lower prices. In the first year of the scheme, households do not pay anything for this shifted energy, therefore total revenue does not increase. In the subsequent years of the program, customers will pay a discounted price, with the discount being made up by Kaluza and government subsidies, generating additional revenue for Kaluza in these years.
Customer savings per month are computed to make sure that there is a benefit to customers for joining this scheme, even after accounting for any potential costs regarding the purchase of smart heaters.
Costs for Kaluza, both before and after the scheme, are calculated to determine whether this scheme can generate additional profits for Kaluza. Costs without the scheme are simply equal to the monthly demand for kWh multiplied by the energy price of £0.12. Costs for Kaluza during this scheme comprise of a fixed and a variable element. The fixed element is made up of the installation costs, the equipment costs, and the maintenance cost, while the variable element depends on the amount of curtailed energy purchased at the discounted purchase price and the amount of that cost that the government will subsidize. We amortize the installation and equipment costs over the course of the first year. The equipment costs for Kaluza depend on the proportion of each heater that the government is willing to fund.
Taking costs from revenue, we get gross profit for Kaluza before the scheme and after implementing the scheme. Kaluza will lose money in the first year of the scheme due to the free curtailed energy being given to households and the installation and equipment costs. However, after this, the scheme will generate additional revenue for the company. Breakeven points can be calculated and will vary depending on the assumptions.
plt.subplots(figsize=(10, 5))
sns.lineplot(data=sc_example, x='dt', y='revenue_pm_scheme', label="Revenue")
sns.lineplot(data=sc_example, x='dt', y='monthly_costs_scheme', label="Costs")
plt.title("Monthly Fluctuations in Revenue and Cost for example scenario")
plt.xlabel("Time")
plt.ylabel("£ per household")
plt.legend()
plt.show()
This graph shows the fluctuations in monthly revenue and monthly cost over a period of two years for an example scenario. Similar patterns are displayed across nearly all scenarios we modelled. We see that over the first year, costs exceed revenues as Kaluza is providing households with free shifted energy. After a year, however, Kaluza will start charging households for this energy, albeit at a discounted price. This causes a sharp increase in revenues that coincides with a fall in costs for Kaluza. This decrease in costs is driven by the installation and equipment costs being fully paid off in the first year. We assume that Kaluza pays for the smart heaters and their installation over the course of the first year of the scheme. The fluctuations in revenue and costs after the first year is driven primarily by seasonal changes in demand, with changes in the amount of shifted energy available also affecting these values to a certain extent. Revenues will intuitively rise in periods with high demand, such as the colder winter months, and fall in the relatively warmer summer months. We see costs follow a similar pattern as they are also driven by demand.
plt.subplots(figsize=(10, 5))
sns.lineplot(data=sc_example, x='dt', y='monthly_profit', label="Monthly Profit")
sns.lineplot(data=sc_example, x='dt', y='monthly_profit_scheme', label="Monthly Profit Scheme")
plt.title("Monthly Changes in Profit for sample scenario")
plt.xlabel("Time")
plt.ylabel("£ per household")
plt.legend()
plt.show()
This graph shows the fluctuations in monthly profit over a period of two years for a sample scenario. Similar patterns are displayed across nearly all scenarios we modeled. In the first year, Kaluza is generating a loss as they are providing households with free shifted energy as well as paying for installation and equipment costs. After a year, however, Kaluza will start charging households a discounted rate for this energy, and equipment and installation fees will be fully paid off, causing profit to be generated. This profit is greater than the profit Kaluza would make if they had not implemented this scheme. The profit from the scheme follows a similar seasonal pattern to the regular profit made by Kaluza, however with slightly more variance as the amount of shifted energy affects revenues under this scheme.
We modeled several scenarios, each made up of different combinations of government subsidies and grants, as well as varying penetration rates and discounts for the consumer. We posit that there would be a positive relationship between penetration rate and total discount, as the cheaper energy is, the more households would join our scheme. Below is a table of the different scenarios we analyzed.
We have outlined 9 different scenarios for our model to compute. These scenarios can be split into 3 main groups according to their penetration rate: 10%, 25%, 50%. Moreover, another parameter that yields significant differences in the feasibility model is heater grant, as installation of smart heaters is a costly process, hence we assume that government will be able to facilitate 50%, 75%, or 100% of the smart heater purchase cost, while Kaluza is going to cover the installation. Finally, the third main parameter is a total discount, which is less significant when compared to the previous two. It determines whether we provide 25%, 50%, or 75% discount on curtailed energy and depends on government subsidies. This leads us to the remaining two parameters which represent subsidies during the installation year and subsidies in the post-installation period.
While the full breakdown of associated changes in revenue, profitability, and cost for each scenario is provided in the appendix, we have decided to provide 3 scenarios for this report showing different combinations of these parameters.
fig, axs = plt.subplots(1,3,figsize=(21, 5))
plot_scenario(sc1, axs[0], 'Scenario 1')
plot_scenario(sc2, axs[1], 'Scenario 6')
plot_scenario(sc3, axs[2], 'Scenario 8')
Scenario 1
This scenario is by far the most conservative, it assumes a 10% penetration rate, which would mean targeting just over 1,000 households, and an initial government grant of £300,000 which would pay half of the smart heater purchase costs. This would leave Kaluza with incredibly high smart heater installation costs of £400 per household spread across the first year. Additionally, as previously mentioned, during the first year of the program all curtailed electricity will be provided for free to the customer and in this scenario, the government will subsidize only 40% of the discount with a follow-up subsidy of 20% for the 25% discount rate on curtailed energy. From the graph, it is apparent that Kaluza would be unable to face such upfront costs and wait 5.5 years to break even. Thus, this scenario is not financially feasible.
Scenario 6
Scenario 6 shows a market penetration rate of 25% meaning that the program would target roughly 2500 households. However, the government would subsidize 100% of the smart heater purchase cost and Kaluza will only have to pay for its installation. The government would also be more generous in offering a 70% subsidy for curtailed energy sales. This is a rather optimistic scenario that would provide customers with curtailed energy at a 50% price of its regular cost and break-even in 3 years and 9 months. Despite a much higher market penetration rate, Kaluza will face fewer upfront costs. The required government grant would amount to £1,500,000 with additional subsidies for discounted curtailed energy. However, this scenario offers a realistic market size and a financially feasible break-even point. If the £1,500,000 government grants proves to be too high, we assume it would be possible to offload 20% of the smart heater purchase costs to the households, thus reducing the initial grant to a more realistic £1,200,000.
Scenario 8
The following scenario is the most ambitious, as it assumes a 50% market penetration rate, which would mean 5,000 households join the scheme. The government would not be able to fully cover smart heater purchase costs for such a high number of people, therefore a 75% smart heater cost grant would be applied instead, resulting in a $1,125,000 grant for year 1. Kaluza would face smart heater purchase and installation costs of £1,250,000 which would not be a feasible investment for a 6-year break-even point.
Overall, scenario 6 seems to be the most appealing option, if the government grant of £1,500,000 appears to be too high, it would be possible to offload some cost of smart heater purchase to the customer.
The table below shows the impact on customers, Kaluza, and the government during the installation year and subsequent years. Government subsidies per household during installation year would amount to £600 for smart heater purchase and £151.40 for discounted electricity with a further annual subsidy of £85.43 per household.
Kaluza on the other hand will see a reduction in profits of £166.08 when compared to providing electricity without shifting curtailed energy. However, in every subsequent year, it would see a £60.78 increase in profits per household from an additional energy source.
Finally, on average, customers will be able to enjoy a £219.96 reduction in their annual electricity bill during the installation year as well as a £109.97 annual reduction in electricity bill despite a 10% increase in usage, through utilizing curtailed energy.
o6 = overview(sc6).reset_index()
table = o6[o6.year.isin([2018, 2019])][['year','customer_savings', 'profit_diff', 'gov_subsidy']]
table.year = ['Installation Year', 'Subsequent Years']
IPython.display.HTML(table.to_html(index=False))
We have seen that currently 310,395 MWh are curtailed annually across the Orkney Isles, which account for about 52% of the total potential energy that can be generated there. This suggests that different actions should be put in place to reduce that amount of curtailed energy so all parties involved can benefit from that energy that would otherwise be wasted. A solution explored in this report is to shift demand from periods where energy is uncurtailed to periods where energy is curtailed. We have seen that with larger demand response penetration rates, more energy can be shifted, and thus more curtailed energy can be used, following a linear trend. However, if a demand penetration rate of 45% was selected, our costs would be too high for the scheme to be financially feasible. After computing our costs, profit, and revenue, we have found that the optimal penetration rate is 25% which would amount to 2,500 households joining the scheme. In this scenario, the government would subsidize 100% of the smart heater purchase cost and Kaluza would only pay the £100 installation fee. The government would offer a 70% subsidy for curtailed energy sales and thus customers would get curtailed energy at a 50% price of its regular cost. This will lead to a break-even on this scheme in 3 years and 9 months. Moreover, this penetration rate of 25% would be a reasonable target to attain since it would offer Kaluza's customer a reduced cost in heating and the possibilities to use more electricity from a renewable source.
[1] Scottish & Southern Electricity Network https://www.ssen.co.uk/anm/orkney/
[2] (2005, October 21) Technical Description Enercon E-44
[3] Orkney’s Fuel Poverty Strategy 2017-2022 https://www.orkney.gov.uk/Files/Consultations/Fuel-Poverty/Draft-Orkney-Fuel-Poverty-Strategy-2017-2022.pdf
[4] (2017, February 15) Scottish Council Areas 2001 to 2011 Census Profile Comparator Tool https://www.scotlandscensus.gov.uk/documents/council_area_profiles/Orkney_Islands.pdf
[5] Dimplex Quantum Heater QM150 https://www.dimplex.co.uk/product/quantum-heater-qm150